package zjl;

import zjl.base.BizRequest;

import java.util.*;

public class GeneticAlgorithm {
    // 随机数生成器
    private Random random = new Random(System.currentTimeMillis());

    // 拓扑资源视图及实时确定性业务需求集合
    private ResourceStore resourceStore;

    // 种群
    private List<Individual> population;

    // 种群大小及种群代数
    private int generationIndex = 0;

    // 最优记录信息
    private double bestFitness = 0.0;
    private Individual bestIndividual = null;

    public GeneticAlgorithm(ResourceStore rs) {
        this.resourceStore = rs;
        this.population = new ArrayList<>();
    }

    private void initPopulation() {
        int count = 0;
        while (count < ConfigData.POPULATION_SIZE) {
            Individual indv = new Individual(this.resourceStore);

            boolean flag = indv.estimate();
            if (!flag)
                continue;

            this.population.add(indv);
            count++;
        }

        Collections.sort(this.population);
    }

    // 使用轮盘赌算法从当代种群中选择一个个体
    private Individual selectIndividualByRouletteWheel() {
        double totalSum = 0.0;
        for (int i = 0; i < ConfigData.POPULATION_SIZE; i++) {
            double fitness = this.population.get(i).getFitness();
            totalSum += fitness;
        }

        double rand = Math.random() * totalSum;
        double partialSum = 0.0;

        for (int i = 0; i < ConfigData.POPULATION_SIZE; i++) {
            Individual iv = this.population.get(i);
            partialSum += iv.getFitness();
            if (partialSum > rand)
                return iv;
        }

        return null;
    }

    // 生成下一代种群
    private void evolvePopulation() {
        int crossCount = (int)(this.population.size() * ConfigData.CROSS_PROBABILITY);

        List<Individual> newPopulation = new ArrayList<>();
        newPopulation.addAll(this.population);

        for (int i = 0; i < crossCount; i++) {
            Individual v1, v2;
            // 使用轮盘赌算法从种群中选出两个父代个体
            do {
                v1 = this.selectIndividualByRouletteWheel();
                v2 = this.selectIndividualByRouletteWheel();
            } while (v1 == null || v2== null || v1 == v2);

            // 染色体交叉产生两个子代个体
            List<Individual> children = v1.crossover(v2);

            // 对子代个体执行可能产生变异操作
            for (Individual iv : children) {
                if (iv.estimate()) {
                    if (Math.random() < ConfigData.MUTATE_PROBABILITY) {
                        Individual miv = iv.mutate();
                        if (miv.estimate()) newPopulation.add(miv);
                        else newPopulation.add(iv);
                    } else {
                        newPopulation.add(iv);
                    }
                }
            }
        }
        Collections.sort(newPopulation);
        if (newPopulation.size() > ConfigData.POPULATION_SIZE)
            this.population = newPopulation.subList(0, ConfigData.POPULATION_SIZE);
        else
            this.population = newPopulation;
    }

    private void estimatePopulation() {
        double tmpBestFitness = this.population.get(0).getFitness();
        Individual tmpBestIndividual = this.population.get(0);

        for (int i = 1; i < ConfigData.POPULATION_SIZE; i++) {
            Individual curr = this.population.get(i);
            if (tmpBestFitness < curr.getFitness()) {
                tmpBestIndividual = curr;
                tmpBestFitness = curr.getFitness();
            }
        }

        if (tmpBestFitness > this.bestFitness) {
            this.bestFitness = tmpBestFitness;
            this.bestIndividual = tmpBestIndividual;
        }
    }

    public void executeGeneticProcess() {
        // 初始化种群
        this.generationIndex = 1;
        // 最初父代
        this.initPopulation();
        this.estimatePopulation();
        // 子代迭代
        while (this.generationIndex < ConfigData.ITERATION_MAXNUM) {
            this.evolvePopulation();
            this.estimatePopulation();
            this.generationIndex++;
        }

        // 为实时确定性业务需求分配对应的传输路径
        for (int i = 0; i < this.resourceStore.getBizRequestSize(); i++) {
            BizRequest request = this.resourceStore.getBizRequestAt(i);
            int pathIndex = this.bestIndividual.getGeneAt(i);
            request.setSelectedPath(request.getKspPath(pathIndex));
        }
    }

    public void printEstimationResult() {
        System.out.println("ESTIMATION RESULT = <");
        System.out.println(this.bestIndividual);
        System.out.println(">");
    }
}
